import pickle
import logging

from sc_engine.messaging.transports import (
    MessagelessRetrievalContext, MessageRetrievalContext, ITransport
)
from amqplib import client_0_8 as amqp

def determine_message_name(message_type):
    module = message_type.__module__
    name = message_type.__name__

    message_name = '.'.join( (module, name) )

    return message_name

    
class AMQPTransport(ITransport):
    """A transport that relies on amqp-lib.
    
    Subscribing to queues works by creating fanout exchanges, one exchange for
    each message type. All messages are considered durable, and the message is
    only acked when the retrieval context (as returned from the retrieve method)
    is exited without failure.
    """
    NO_EXCHANGE_ERROR_MESSAGE = 'NOT_FOUND - no exchange'
    NO_EXCHANGE_ERROR_NUMBER = 404

    NO_EXCHANGE_LOG_MESSAGE = "No exchange found for %s, message not sent."

    def __init__(self, host, virtual_host, userid='guest', password='guest'):
        self.conn = amqp.Connection(host=host, virtual_host=virtual_host,
            userid=userid, password=password)
        self.logger = logging.getLogger('AMQPTransport')

    def add_queue(self, queue_name):
        self.logger.debug("Adding queue %s" % queue_name)
        chan = self.conn.channel()
        try:
            chan.queue_declare(queue=queue_name,
                durable=True,
                exclusive=False,
                auto_delete=False)
        finally:
            chan.close()

    def subscribe(self, queue_name, message_type):
        chan = self.conn.channel()
        try:
            message_name = determine_message_name(message_type)

            msg = "Adding exchange for queue %s message %s" % (queue_name, message_name)
            self.logger.debug(msg)

            chan.exchange_declare(exchange=message_name,
                type='fanout',
                durable=True,
                auto_delete=False)

            chan.queue_bind(queue=queue_name, exchange=message_name)
        finally:
            chan.close()

    def _handle_amqp_exception(self, e, message_name):
        if e.amqp_reply_code == self.NO_EXCHANGE_ERROR_NUMBER and\
            e.amqp_reply_text.startswith(self.NO_EXCHANGE_ERROR_MESSAGE):

            self.logger.warn(self.NO_EXCHANGE_LOG_MESSAGE % message_name)
            return True
        
        return False

    def send(self, msg):
        chan = self.conn.channel()
        try:
            message_name = determine_message_name(type(msg))

            amqp_msg = amqp.Message(pickle.dumps(msg))
            amqp_msg.properties['delivery_mode'] = 2
      
            chan.basic_publish(amqp_msg, exchange=message_name)
        finally:
            try:
                chan.close()
            except amqp.AMQPException as e:
                if not self._handle_amqp_exception(e, message_name):
                    raise


    def retrieve(self, queue_name):
        self.logger.debug("Retrieving from queue %s" % queue_name)
        chan = self.conn.channel()

        msg = chan.basic_get(queue=queue_name)

        if not msg:
            chan.close()
            return MessagelessRetrievalContext()

        def on_completion(x):
            chan.basic_ack(msg.delivery_tag)
            chan.close()

        in_msg = pickle.loads(msg.body)

        return MessageRetrievalContext(in_msg, on_completion)

    def close(self):
        self.conn.close()
